RAGの実案件に取り組んできた今までの知見をまとめてみた
はじめに
新規事業部 生成AIチーム 山本です。
ChatGPT(OpenAI API)をはじめとしたAIの言語モデル(Large Language Model:以下、LLM)を使用して、チャットボットを構築するケースが増えています。通常、LLMが学習したときのデータに含まれている内容以外に関する質問には回答ができません。そのため、例えば社内システムに関するチャットボットを作成しようとしても、素のLLMでは質問に対してわからないという回答や異なる知識に基づいた回答が(当然ながら)得られてしまいます。
この問題を解決する方法として、Retrieval Augmented Generation(以下、RAG)という手法がよく使用されます。RAGでは、ユーザからの質問に回答するために必要そうな内容が書かれた文章を検索し、その文章をLLMへの入力(プロンプト)に付け加えて渡すことで、ユーザが欲しい情報に関して回答させることができます。
以前の記事では、社内のドキュメント情報に関してQAできるチャットボット(Slackアプリ)を構築した際の、構成方法や試してみての改善点について記載しました。
RAGを使った社内情報を回答できる生成AIボットで業務効率化してみた | DevelopersIO
【Bedrock / Claude】AWSオンリーでRAGを使った生成AIボットを構築してみた【Kendra】 | DevelopersIO
また、ありがたいことにお客様からお仕事もいただき、実際の案件としてRAGを使ったチャットボットを構築する機会がありました。
そんな中で、社内でRAGに関する勉強会を開く機会があり、そこで今までの知見(的なもの)を資料にまとめました。この記事では、その資料に補足を加えながら、筆者が把握しているRAGを使ってシステムを提供する際の現状や課題感について記載します。
資料
以下では各スライドに補足を加えながら、説明していきます
注意点
2023年7月くらいから取り組んでいるので、古い情報も入っているかもしれません。各サービスの最新情報が必要な場合は適宜検索してください。
前提
この資料は以下のような人向けの資料です
- 前提知識
- 一回RAGを触ったことがある
- 「検索を使って、結果と質問を使いLLMにわたす」ことがわかっている
- 一回RAGを触ったことがある
- 状況
- まずはPoCをやってみたいお客様がいる
- 社内である業務のQAシステムをつくる
- 例えば(あくまで一例です)
- 期間:1ヶ月(or 2ヶ月)
- エンジニア:1名
- 状況:生成AIの活用を始めようとしている、始めたばかり
- 制約・条件・方向性
- 汎用性(再現性)が高い
- 様々な状況で使えるように
- こうしたお客様に、まず早く提供する方法、が主眼
逆に言うと、もっと長い期間(半年とか)取れる、もっとエンジニアの時間が取れる、個別で最適なものをつくりたい、という場合はもっと違う考え方になるかと思います
LLMの問題点・RAGの目的
基本のおさらいです
- 問題点
- LLMは学習したことを使って回答が可能です
- 例:プログラミング、
- しかし、LLMが学習していない内容は回答できません(当然ながら)
- 例:ある会社の社内情報、ある部署での手続き方法
- LLMは学習したことを使って回答が可能です
- RAGの目的:
- こうしたLLMが学習していないデータに関して、LLMを使って回答させたい
- そのために、参考ドキュメントを用意して、そこを検索した結果を質問とともにLLMに渡す、という方式を利用する
※ 質問に回答させることだけでなく、翻訳させたりなにか作業をさせることにも使えます
他手法との使い分け(fine-tuningとの違い)
RAGと他の手法は使いわけることが大事だと思います
- LLMの階層
- LLMにはいくつかのレイヤーがある
- 上の方が比較的簡単に変えられ、下の方が変えるのは難しい
(と捉えられる、実際にモデルのどの部分が対応しているかはわかりません)
-
それぞれについて、適切な方法がある
- 役割・立場・内容:プロンプトで指示する
- 口調・スタイル:簡単なfine-tuningを使う
- 知識・ナレッジ:RAGを使う
- 文法・言語・論理的思考:大規模なデータセットで、再度学習する
- QAのような場面だと、知識・ナレッジを新規に埋め込むので、RAGが適している
- fine-tuningをしてしまうと、すべてのレイヤーに影響が起きてしまうので、適してなさそう
※ fine-tuningについては、「補足」の項目で後述します
(山本の現状の感覚です、RAG以外の方がうまくいくケースもあるかもしれません)
体験の目標レベル
「前提」で述べたような「状況」で、RAGを使ったQAチャットボットを作成する場合、実現可能なレベルはユーザが思っているほど高くない(逆に言うと、期待値が高い)です
- [1番目] 実場面の多くのケースでは、(例えば総務部門の方が)人手で質問に回答しています。こうしたケースでは、業務や社内の状況に詳しい方が対応してくれるので、回答を間違えることもないと思われます。また、質問者の状況に応じてフォローしてくれたり、質問に無い内容でも補足してくれることもあるなど、単純なQAだけでなく対話としても良い体験を提供されていると思われます
ただ、生成AIを使ったとしても、ここを目指すのはかなりハードルが高いです。単純なRAGのシステムでは実現が難しく、様々な要素を追加する必要があります。これは「前提」で述べたような状況では、時間や工数が不足することが見込まれます
-
[2番目] (できればここらへんを目指したい)
- [3番目] 現実的には、ドキュメントを初めて渡された人が中を検索しながら回答する、くらいのレベルになることがほとんどです
お客様やユーザが想定する(慣れている)レベルと、提供できるレベルの間とに、ギャップがある場合がほとんどなので、このあたりを予め説明して、合意を取っておく必要があるでしょう
提供するユーザ体験
提供するユーザ体験(機能)としては、まず一問一答を作るところからスタートするのが良いと思います。まずここでベースになるので、良い回答が得られないと(他の機能をつけても)結局ユーザにとっては使いにくいものになってしまいます
また、開発に慣れていない状態や理解が進んでいない状態でさまざまな機能をつけてしまうと、うまく回答できなかったときの分析で、どこから調べたらいいのか・どう改善すれば良いのかのあたりがつけにくくなってしまいます
一問一答を作っていく中で、RAGのベーシックな構成(「LLMの問題点・RAGの目的」の図中の下)構築し、ドキュメントを整備したり、検索・回答生成機能を作りこむことになり、基礎部分が出来た状態で以降の開発に進めます
※ 補足:もちろん要件や要求される仕様に応じて、機能を選択することが大事です
基礎編
「基礎編」では、前スライドのような、現実的な精度・体験レベルの、一問一答の機能を作成することを目的として、考慮・検討するべき点や方針について説明します
RAGのシステム
「LLMの問題点・RAGの目的」で述べた方式を実現するためのベーシックな構成としては、上図のようになります
RAGを改善をするには、以下のように分担することになるかと思います
- 参考ドキュメントの部分は作成者・管理者に拡充をお願いする
- プログラム・検索システム・LLMの部分は開発側が工夫する
この記事では、システム側(開発側)の工夫ポイントについて記載します
RAGのコアな部分
RAGのコアの部分は、回答を生成させる箇所です。関連テキストと質問を貼り付けたプロンプトをLLMに入力して、回答を出力させます。この中での改善ポイントとして、以下の3つがあります。
- 方針1:LLMは性能の高いモデルを使う
- 方針2:プロンプトを工夫する(いわゆるプロンプトエンジニアリングを行い、指示の書き方やテキストの構成を見直す)
- 方針3:関連情報(関連テキスト)の渡し方を工夫する
この内、方針1・方針2については、ある程度固定した方が進めやすいです。具体的には、方針1では、世の中の高性能なモデルから1つ選び、まずはそれを使い続けると進めやすいです。また、方針2では、世の中のプロンプトエンジニアリングや、モデルごとのガイドラインに沿って作成し、あとは適宜修正する、くらいの感覚が良いと思います
方針3は、上記2つよりも回答に大きく影響し、かつ、難しい箇所が多いため、こちらに時間をかけた方が良いと思います
補足:失敗ケース
以前自分が取り組んだ際に、うまく回答ができなかったケースについて、以下のブログで記載しました
多くの場合、該当する情報(ドキュメント)が無い、ドキュメントはあるが読み込めていない、といった、ドキュメントに起因する割合が高かったです(逆に言うと、モデルやプロンプトの構造が原因で回答来なかった割合は低かったです)
方針1:いいモデルを使う
どのモデルを使うのかに関してです。基本的にはGPT4を使うのがオススメです。Claude 2も質問に対する回答を出力しますが、説明の仕方に違和感があるケースが見受けられました。ある案件において回答結果を両者で比較したときも、GPT4の方が回答の方がロジカルな印象を受ける、という分析になりました
システムをAWS内部で閉じたい、海外にデータを送信したくないなどの要件が決まっている場合には、Bedrockの日本リージョンでClaude2を使うのが良いかと思います
Geminiはまだ業務や案件で、RAG(QAチャットボット)として使用していないので、まだ感覚が掴めていません
※ 点数は筆者の感覚値です。条件を整えた正確な比較ではありませんので、ご注意ください。数値評価をしたわけではありません。使用したプロンプトも、各モデルのガイドラインに厳密に合わせたものではありません。プロンプト・関連テキスト・回答言語はすべて日本語でした(精度が高いと言われる英語ではありません)。また、プロンプトは改良を重ねたものではなく、ある程度のレベルのものです。また、今後上記のモデルも改良されていくと思われるので、利用時に再度試してから、選択するのが良いでしょう。
※ LLMはオープンソースでたくさん公開されているものがありますが、まだこれらを使ったRAGには取り組めていません。もしかしたらより適したLLMがあるかもしれません。
方針2:プロンプトの全体を工夫する
当初は上記のようなプロンプト(のテンプレート)を使用していました。あまり凝っているとは言えませんが、これでも十分な回答が得られるケースがほとんどでした。「補足:失敗ケース」で述べたように、うまく回答できなかった原因は、プロンプトの構成や書き方よりも、情報(ドキュメント)が無いケースが大半だったため、プロンプトを頑張って改良するのは優先度が低い、という印象です
※ 補足
プロンプトエンジニアリングの共通項
- https://arxiv.org/abs/2312.16171
- 「プロンプトエンジニアリング 26の原則」でWeb検索しても、解説記事がヒットしますので、そちらを見ていただくとわかりやすいかと思います
モデルごとのガイドライン(例)
- OpenAI:https://platform.openai.com/docs/guides/prompt-engineering
- Anthropic:https://docs.anthropic.com/claude/docs/guide-to-anthropics-prompt-engineering-resources
最近はaws-samplesの例を参考に、プロンプトを改良したものを使用しています
方針3:関連情報の渡し方を工夫する
関連情報の渡し方(つまり、関連テキストをどのようにプロンプトに入れるのか)に関して、以下の2に分けて考えるとわかりやすいと思います
- 量:できる限り多くのドキュメントを検索し、取り出した関連テキストをできるかぎり多くプロンプトに入れることで、情報の抜けがないようにする(検索におけるカバー率を上げる)
- 質:そもそも用意するドキュメントの種類を増やしたり、ドキュメントの読み込み方を工夫する(ドキュメント自体や前処理の話)
※ 補足:厳密な分け方ではなく、おおまかな考え方としての分け方です
方針3-1:関連情報の「量」を増やす
LLMは賢く、無駄な情報は無視して回答してくれるので、情報が多くても問題ないケースがほとんどです(※ ただし、誤解しないようにプロンプトの書き方を気をつければ。後述)。逆に必要な情報がないと、おかしな回答をしたり、一般的な内容を回答してしまうケースが多かったです。なので、基本的な方針としては「量を増やす」という考え方がオススメです
ただし、以下のような制約やトレードオフがあるので、これらを考慮して入れるべき量を決めると良いでしょう
- コスト:トークン数が増えるとLLMの利用料が増えます。使用するドキュメント件数は、上限を考慮して決める必要があります
- 最大トークン数制限:(コストは問題なくても)ドキュメントの量が多いとAPIのトークン数制限を超えてしまいます。その際には、中間にドキュメントを圧縮する処理を追加するという方法が利用できます
補足:処理時間
LLMの処理時間(実行開始してから、回答が終了するまでの時間)は、出力トークン数にほぼ比例しています。なので、ドキュメントを増やす = 入力トークンを増やすこと自体は、処理時間に与える影響は小さいです
(なので、処理時間的にはドキュメントを増やしても問題ないので、(他の点を考慮しながら)できるかぎり多くのドキュメントを入れると良いでしょう)
方針3-1-1:検索件数を増やす
社内のQAボットで試用した中で、検索結果の上位9番目や10番目のドキュメントが使われるケースがそこそこありました。
まずは10件程度で試してみて、そこから調整すると良さそうです
方針3-1-2:検索システムの仕様を見直す
同じサービスを使っていても、メソッドによって挙動が変わるケースがあります。例えば、Amazon Kendraの場合、文章を検索するAPIとして、queryメソッドとretrieveメソッドがありますが、上記のようにテキストの取り出され方が異なります。
各サービスのメソッドの仕様を確認し、見落としが少ないメソッドを選ぶと良いでしょう
※ 補足:queryメソッドでは、質問に対する(仮の)回答を自動生成するなど、retrieveメソッドには無い機能を持っているので、一概にretriveの方が良い、というわけではありません
Kendraのqueryのような挙動をするメソッドを使う必要がある場合、ファイルを分割することで、見落としを小さくするという手もあります
方針3-1-3:情報を圧縮する
大量の関連テキストを使用したい場合、中間に圧縮処理を挟むという手があります。簡単に言うと、ドキュメント1件ずつ質問に関連する箇所を要約させる処理を追加し(Read)、その要約結果を関連テキストとして、回答生成用のプロンプトに入力する、というものです
処理の詳細は以下の記事を見てください。
この処理の目的やメリット・デメリットは上記のとおりです。特に中間処理を挟むことで、回答のストリーミングが開始されるまでの時間がかなり長くなる(長いと20秒以上かかる)ので、ユーザの印象が大きく悪くなることがあります。(なので、通常のQAボットではあまりオススメはしません)
Read処理はGPT3.5のような性能が低めのモデルでも、回答生成処理がGPT4であれば、そこまで問題にならないケースが多かったです。コスト削減や処理時間短縮のために性能が低めのモデルを使っても良いと思います
また、この処理はモデルをfine-tuningさせる方法も使えました。詳細は以下をご覧ください
https://dev.classmethod.jp/articles/speed-up-qa-bot-with-fine-tuning/
今まで使用した中の感覚として、回答開始が遅くなるのは、ユーザにかなり悪い印象を与えます。また、LLMはどんどん改良されて最大トークン数が拡張されています。現状だと128kトークン入れれるので、ほとんどのケースで十分なことが多く、Read処理を入れる必要がありません。なので、リアルタイム性が必要ないユースケースや、どうしても大量のドキュメントを読ませる必要があるときのみ、使用すると良さそうです
方針3-2:ドキュメントの「質」をあげる
ここから「質」として上記の2点について説明します
方針3-2-1:テキストの取り出し方を改良する
いわゆるエンタープライズ検索サービス(Amazon Kendra・Azure AI Searh・Vertex AI search)を使う時、既存のドキュメントを検索用インデックスにインポートします。このインポートでは、ファイルがに何らかの前処理がされ、テキストとして読み込まれます。この読み込まれ方が、ファイルごとに異なり、人間が意図するような読み込まれ方にならないことがあります。
例えば、PDFファイルでは、ページ数やフッターが本文テキストの間に入り込んだり(ページ間で分断されてしまう)、長いCSVファイルを読みこむと、検索するときにヘッダ部分が抜けてしまうことがあります。Markdown形式(テキスト形式)はそのまま読みこまれるため、変な齟齬が生じにくく、実際に社内ボットで試していても、おかしな回答が少ない印象です
どのように読み込まれるか確認するには、実際に検索を実行して、出力結果を見てみる必要があります。その上で前処理で対応することになります。この前処理はドキュメントの内容に依存するため、統一的な処理が難しく個別に対応することが多いです
この点については、お客様と合意を取る必要がありそうな点です。ほとんどのケースで、既存ドキュメントのうち、ワードやパワーポイント・エクセル・PDFファイルの割合が高いです。そうすると、(意図しない読み込まれ方がされにくい)Markdownやテキストファイルへの変換が難しく、そのまま入力することになり、より良い回答を得るためには前処理が必要になります。どういうデータがあるのか把握し、用意するデータを変えるのか、もしくは、どこまで個別対応するのかを決める必要があります
方針3-2-2:暗黙的な情報を追加する
今までは開発(エンジニアリング)という色合いが強かったですが、ここからはやってみて・分析・改良していくという研究開発的な感覚が強いです
この説明のために、まず「QAに関連する情報」の前提について説明します。
QAで人間が回答するときに考慮に入れる情報(知識)は、上図のようになっていると考えられます。各項目は以下のとおりです。
- 業務知識:各業務部門に関する知識。上の例で言うとこと。多くの場合、ドキュメントに明示されている内容。
- 社内ルール:社内で共通して使用する用語・部署名などの用語を含むもの。上の例で言うと、こと。ドキュメントに明示されていたり、しなかったりする。また、多くのドキュメントで使われている内容で検索にヒットしにくい。
- 業界の常識:暗黙的に理解していること。多くの場合、ドキュメントにかかれていない。
例えば、「日比谷に行く時の交通費申請を教えてください」という質問があったとき、それぞれ以下のような情報が該当すると思います
- 業務知識:「オフィスへの通勤費は〇〇という科目で登録する」「出張の場合は上長に承認を受け、〇〇という科目で登録する」
- 社内ルール:「クラスメソッドの経費申請はMoneyForwardというシステムを利用している」「毎月の月末を締め日として、翌月はじめに申請する」「日比谷オフィスというオフィスがあること」
- 業界の知識:「そもそも交通費は立替払いをするかどうか」(他にもたくさんあると思われます)
人間がドキュメントを読む時、ドキュメント本文以外にも、メタデータやコンテキストを考慮しているでしょう
ドキュメント本文も、テキストだけでなく、画像も理解したり、リンクがあればリンク先にアクセスしてその内容も理解した上で、回答すると考えられます
ユーザの質問に答える時、質問の本文以外にも考慮されている情報がありそうです。例えば、メタデータとして、質問している人の名前や所属・属性、質問してきた日時を考慮していそうです。また、コンテキストとして、(今まで会話したことがある人だったら)質問した人と回答する人の関係性や、今までの会話の内容(スレッド内・スレッド外)の情報も考慮されていそうです
ここまでの人間が利用している情報を並べると、上図のとおりです。この内、通常のQAシステム(ベーシックなRAGの構成)では、緑で囲った部分が対象の範囲です。人間と比較して、一部の情報しか対象になってなく、利用できる情報が少ないと考えられます。QAシステムの回答をより良くするには、この中で足りない情報を付け加えることが効果的(必須)です。
補足:(LLMが学習対象としたデータに依存しますが)LLMは社会の常識や業界の常識(といえる内容)を理解していると考えられるため、ある程度は対応できそうです。ただし、会社独自の知識や業務知識には対応できません
参考:https://dev.classmethod.jp/articles/discussion-on-needs-for-g-of-rag/
それぞれの情報をどのように追加するかは、上図の方法があり得ると思われます。ただし、それぞれの方法において、制約を超えないか検討が必要です(=全部実装しようとすると大変です)
効果がわかりやすく、実装し易い一例として、ファイルパス(ドキュメントのメタデータの一部)を利用する方法があります。回答生成のプロンプトに関連テキストに入力するときに、そのパスを明示するという方法です。これによって、生成AIがパスも考慮して、情報を取捨選択できます
これは案レベルで、(おそらく)世の中で広くは利用されていないものです。ワードファイルのような場合、前処理で章ごとに分割して、ファイル名を章タイトルをつけるという方法が考えられます
ドキュメントの画像やリンク先を読ませるには、ドキュメントのローダを作成する方法が挙げられます。しかし、実装工数が増える点と、エンタープライズ検索としてのメリットが薄くなるため、検索システム自体を自作したほうが良いことになるかもしれません
まとめ
補足
fine-tuningの難しさ
現状で提供されているfine-tuning用のAPIでは、データ形式は入力と出力をテキストとして指定します。このとき学習される範囲というのは指定できません。
LLMには図のような要素があると考えられますが、これらを個別に調整できるわけではなく、全体が変わってしまいます
RAGでは、知識・ナレッジのみを追加できればいいのですが、上記のような点が難しいです。
特に、一問一答クイズのようなFAQを作ってfine-tuningさせると、回答が単語だけになってしまう、というように知識以外も変わってしまう、ということがありました。どのようなやり方・データでfine-tuningすれば知識のみを追加できるのか、さらに調査する必要があります
また、fine-tuningを使うと、上記の点がプロジェクトを進める上で判断が難しいです
RAGの方が分析や検証がしやすく、また後処理と組み合わせることで、最新のドキュメントへの参考文献もリンクできるので、ユーザが確認できます。こうしたシステムの方が、組織として導入の判断がしやすいという面もあります
社内知識を使った回答の難しさ
弊社内の言い方として「〇〇期」という言い方があります。これは創業年を1期として、何年目を指す言葉です(20期は2023年7月~2024年6月を指します)。ユーザからの質問として「20期の年末年始のスケジュールを教えて」と聞かれたときに、検索結果で「2023年の年末年始スケジュール」「2022年の年末年始スケジュール」のドキュメントがヒットしたとします。このとき、LLMは20期が何を指すのかわからないので、どちらを参照するべきかわかりません
こうした20期が社内知識に該当する部分です。社内用語を説明するドキュメントは用意されていないケースが多いです。また、ドキュメントがあったとしても、こうした用語は多くのドキュメントで使用されているため、説明用ドキュメントが検索で引っかかりにくいという性質があります
対策としては、社内知識に該当する部分は、別途ドキュメントと検索システムを用意する方法が挙げられます。しかし、いくつか問題点がありそうです
ベクトル検索の難しい点
文章検索ではベクトル(密ベクトル)検索が使われることが多いです。この密ベクトルは「意味的に近い」ものを探すためのもので、「質問に対して回答できる」ものを探すものではありません。なので、単純な文章検索では対応できない点もありそうです
https://dev.classmethod.jp/articles/vector-search-except-and-numerical-big-small/
https://dev.classmethod.jp/articles/problem-and-improve-methods-of-vector-search/
体験の話
詳細は以下の記事をご覧ください
テキストの取り出し方
各サービスとメソッドや検索種類によって、テキストの取り出され方が異なるので、注意して使用する必要があります
発展編
ドキュメント周りを中心に記載しましたが、それ以外にも改良ポイントはありそうです。詳細は以下の記事をご覧ください
ベーシックなRAGの構成をもとに、どんどん改良が必要だろうと考えています。一例ですが、上記のように、ドキュメント検索以外の検索システムを付け加えたり、対話ができるような機能が必要だと思います。また、ユーザに対してオンボーディングや使い方の説明だったり、ドキュメントのどの箇所をどのように修正するべきかをフィードバックする仕組みづくりも必要そうです
QAとして人と対話するときは、単純に情報をもとに回答するだけでなく、上記のようなやりとりがあると思われます
https://dev.classmethod.jp/articles/discussion-on-needs-for-g-of-rag/
こうした対話をさせる方法として、ユーザの意図をLLMに推定させることも挙げられます。詳細は、以下の記事をご覧ください
https://dev.classmethod.jp/articles/estimate-user-intention-in-genai-bot-with-rag/